#ifndef _RETRO_DATA
#define _RETRO_DATA

#pragma region Constants
#define RETRO_MAXTEXT               (100 + 1)
#define RETRO_MAXNAME               (50 + 1)
#define RETRO_MAP_MINWIDTH          10
#define RETRO_MAP_MINHEIGHT         10
#define RETRO_MAP_MINLAYERS         1
#define RETRO_MAP_MAXLAYERS         100

#define RETRO_SPRITEMAP_DEFWIDTH    800
#define RETRO_SPRITEMAP_DEFHEIGHT   600
#define RETRO_SPRITEMAP_DEFLAYERS   3
#define RETRO_SPRITEMAP_MAXWIDTH    999999
#define RETRO_SPRITEMAP_MAXHEIGHT   999999

#define RETRO_GRIDMAP_DEFWIDTH      20
#define RETRO_GRIDMAP_DEFHEIGHT     15
#define RETRO_GRIDMAP_DEFLAYERS     3
#define RETRO_GRIDMAP_MAXWIDTH      200
#define RETRO_GRIDMAP_MAXHEIGHT     200

#define RETRO_GRIDMAP_DEFTILESIZE   32
#define RETRO_GRIDMAP_MINTILESIZE   8
#define RETRO_GRIDMAP_MAXTILESIZE   64

#define RETRO_PROJECTFILE           _T("project.retro")
#define RETRO_RESOURCEEXT           _T(".res")
#define RETRO_RESOURCENAMEFORMAT    _T("%.4d.%s") ## RETRO_RESOURCEEXT
#define RETRO_RESOURCEFIND          _T("*") ## RETRO_RESOURCEEXT

#define RETRO_TILESET_EXT           _T("tiles")

#define RETRO_RESOURCE_INITIALIZED  0x00000001
#define RETRO_RESOURCE_UNSAVED      0x00000002
#define RETRO_RESOURCE_OPENED       0x00000004
#define RETRO_RESOURCE_DELETED      0x00000008
#define RETRO_RESOURCE_CORRUPTED    0x00000010

#define SPRITEDATA_SIMPLE           0x00000000
#define SPRITEDATA_COMPLEX          0x00000001
#define SPRITEDATA_GROUP            0x00000002
#define SPRITEDATA_TILE             0x00000010
#define SPRITEDATA_CHILD            0x00001000
#define SPRITEDATA_LASTCHILD        0x00002000

#define TILEDATA_LEFT               0x00010000
#define TILEDATA_RIGHT              0x00020000
#define TILEDATA_UP                 0x00040000
#define TILEDATA_DOWN               0x00080000
#define TILEDATA_ALL                0x000F0000

#pragma endregion

namespace Retro
{
  namespace Data
  {
    enum ResourceType
    {
      ResourceSpriteMap,
      ResourceGridMap,
      ResourceTexture,
      ResourceSprite,
      ResourceAudio,
      ResourceScript,
      ResourceFirst = ResourceSpriteMap,
      ResourceLast = ResourceScript,
    };

    enum ResourceFolders
    {
      FolderMap,
      FolderTexture,
      FolderSprite,
      FolderAudio,
      FolderScript,
      FolderFirst = FolderMap,
      FolderLast = FolderScript,
    };

    enum ScriptLanguage
    {
      ScriptUnknown,
      ScriptLua,
    };

    struct DLLEXPORT Resource
    {
    public:
      struct Header
      {
        INT             id;
        ResourceType    type;
        TCHAR           name[RETRO_MAXNAME];
      }
      headerResource;

      INT flags;

      INL Resource() : flags(0)
      {
        headerResource.id       = 0;
        headerResource.type     = ResourceFirst;
        headerResource.name[0]  = '\0';
      }

      INL PCTSTR getName() const
      {
        return headerResource.name;
      }
      INL VOID  setName(PCTSTR value)
      {
        memcpy(headerResource.name, value, sizeof(TCHAR) * RETRO_MAXNAME);
      }

      INL INT   getID() const
      {
        return headerResource.id;
      }
      INL ResourceType getType() const
      {
        return headerResource.type;
      }
      INL INT   getFlags() const
      {
        return flags;
      }

      INL BOOL  isInitialized() const
      {
        return (flags & RETRO_RESOURCE_INITIALIZED) == 0;
      }
      INL BOOL  isSaved() const
      {
        return (flags & RETRO_RESOURCE_UNSAVED) == 0;
      }
      INL BOOL  isUnsaved() const
      {
        return (flags & RETRO_RESOURCE_UNSAVED) > 0;
      }
      INL BOOL  isOpened() const
      {
        return (flags & RETRO_RESOURCE_OPENED) > 0;
      }
      INL BOOL  isDeleted() const
      {
        return (flags & RETRO_RESOURCE_DELETED) > 0;
      }
      INL BOOL  isCorrupted() const
      {
        return (flags & RETRO_RESOURCE_CORRUPTED) > 0;
      }

      INL VOID  setInitialized()
      {
        flags |= RETRO_RESOURCE_INITIALIZED;
      }
      INL VOID  setUninitialized()
      {
        flags &= ~RETRO_RESOURCE_INITIALIZED;
      }
      INL VOID  setSaved()
      {
        flags &= ~RETRO_RESOURCE_UNSAVED;
      }
      INL VOID  setUnsaved()
      {
        flags |= RETRO_RESOURCE_UNSAVED;
      }
      INL VOID  setClosed()
      {
        flags &= ~RETRO_RESOURCE_OPENED;
      }
      INL VOID  setOpened()
      {
        flags |= RETRO_RESOURCE_OPENED;
      }
      INL VOID  setDeleted()
      {
        flags |= RETRO_RESOURCE_DELETED;
      }
      INL VOID  setRecovered()
      {
        flags &= ~RETRO_RESOURCE_DELETED;
      }
      INL VOID  setCorrupted()
      {
        flags |= RETRO_RESOURCE_CORRUPTED;
      }

      BOOL save();
      virtual BOOL save(HANDLE iFile);
      virtual BOOL open(HANDLE iFile) = 0;
      virtual VOID free() = 0;
      virtual VOID deleteFile();
    };

    struct TileData
    {
      INT flags;
      INT tag;
    };

    struct GridCell
    {
      INT texture;
    };

    enum TextureType
    {
      TextureImage,
      TextureTileset,
      TextureAnimation,
    };

    struct DLLEXPORT Script : Resource
    {
    public:
      struct Header
      {
        ScriptLanguage lang;
      }
      headerScript;

      PCHAR buffer;

      virtual BOOL save(HANDLE iFile);
      virtual BOOL open(HANDLE iFile);
      virtual VOID free();

      INL PCSTR getBuffer() const
      {
        return buffer;
      }
      INL VOID  setBuffer(PCSTR iValue)
      {
        if (buffer) delete[] buffer;
        INT mLength = (INT)strlen(iValue) + 1;
        memcpy(buffer = new CHAR[mLength], iValue, mLength * sizeof(CHAR));
      }

      INL ScriptLanguage getLanguage() const
      {
        return headerScript.lang;
      }

      INL VOID setLanguage(ScriptLanguage iValue)
      {
        headerScript.lang = iValue;
      }

      INL operator PCSTR() const
      {
        return buffer;
      }

      INL Script()
      {
        buffer = NULL;
      }
    };

    struct DLLEXPORT Tileset
    {
      TileData* data;

      INL Tileset()
      {
        data = NULL;
      }

      INL ~Tileset()
      {
        delete[] data;
      }
    };

    struct DLLEXPORT Texture : Resource
    {
    private:
      Gdip::Bitmap*   bitmap;
      Tileset*        tileset;

    public:
      struct Header
      {
        TCHAR path[MAX_PATH];

        union DataTexture
        {
          struct DataTileset
          {
            INT tileSize;
          }
          tileset;

          struct DataAnimation
          {
            INT cx;
            INT cy;
          }
          animation;

          BYTE raw[32];
        }
        data;

        TextureType type;
      }
      headerTexture;

      union
      {
        struct
        {
          LONG   width;
          LONG   height;
        };

        Geom::Size  size;
      };

      INL PCTSTR      getPath() const
      {
        return headerTexture.path;
      }

      VOID  setPath(PCTSTR value);

      INL TextureType getTextureType() const
      {
        return headerTexture.type;
      }

      VOID  setTextureType(TextureType value);

      INL Gdip::Bitmap* getBitmap()
      {
        return bitmap;
      }

      INL const Tileset* getTileset() const
      {
        return tileset;
      }

      INL BYTE*       getDataRaw()
      {
        return headerTexture.data.raw;
      }

      INL Texture::Header::DataTexture::DataAnimation&  getDataAnimation()
      {
        return headerTexture.data.animation;
      }

      INL Texture::Header::DataTexture::DataTileset&    getDataTileset()
      {
        return headerTexture.data.tileset;
      }

      INL VOID        getTilesetFilename(TCHAR (&oBuffer)[MAX_PATH]) const
      {
        _stprintf_s(oBuffer, _T("%.4d.%s"), getID(), RETRO_TILESET_EXT);
      }

      virtual BOOL    save(HANDLE iFile);
      virtual BOOL    open(HANDLE iFile);
      virtual VOID    free();
      virtual VOID    deleteFile();

      INL Texture()
      {
        headerTexture.path[0] = '\0';
        tileset = NULL;
        bitmap  = NULL;
        width   = 0;
        height  = 0;
      }
    };

    struct DLLEXPORT Map : Resource
    {
    public:
      struct Header
      {
        INT layers;
        INT width;
        INT height;
      }
      headerMap;

      INL INT getLayers() const
      {
        return headerMap.layers;
      }
      INL INT getWidth() const
      {
        return headerMap.width;
      }
      INL INT getHeight() const
      {
        return headerMap.height;
      }

      INL Map()
      {
        headerMap.width     = RETRO_GRIDMAP_DEFWIDTH;
        headerMap.height    = RETRO_GRIDMAP_DEFHEIGHT;
        headerMap.layers    = RETRO_GRIDMAP_DEFLAYERS;
      }

      virtual BOOL save(HANDLE iFile);
      virtual BOOL open(HANDLE iFile);
      virtual VOID setSize(INT cx, INT cy, INT layers);
    };

    struct DLLEXPORT GridMap : Map
    {
    public:
      struct Header
      {
        INT tilesetID;
      }
      headerGrid;

      GridCell** pGrid;

      INL GridCell** getGrid() const
      {
        return pGrid;
      }
      INL INT  getTilesetID() const
      {
        return headerGrid.tilesetID;
      }
      INL VOID setTilesetID(INT value)
      {
        headerGrid.tilesetID = value;
      }

      virtual BOOL save(HANDLE iFile);
      virtual BOOL open(HANDLE iFile);
      virtual VOID free();
      virtual VOID setSize(INT cx, INT cy, INT layers);

      INL GridMap()
      {
        pGrid    = NULL;
        headerGrid.tilesetID = 0;
      }
    };

    enum SpriteType
    {
      SpriteInvalid,
      SpriteTexture,
      SpriteRect,
      SpriteLine,
    };

    enum ColorIndex
    {
      ColorTopLeft      = 0,
      ColorTopRight     = 1,
      ColorBottomLeft   = 2,
      ColorBottomRight  = 3,
      ColorFirst        = ColorTopLeft,
      ColorLast         = ColorBottomRight,
    };

    struct SpriteData
    {
      union
      {
        BYTE data[128];

        struct
        {
          INT         flags;
          INT         id;
          SpriteType  type;

          union
          {
            struct
            {
              Geom::Point   start;
              Geom::Point   end;
            };

            struct
            {
              Geom::Bounds  source;
              Geom::Bounds  bounds;
            };

            Geom::Point point[8];
          };

          REAL  rotate;
          REAL  scaleX;
          REAL  scaleY;

          union
          {
            struct
            {
              Geom::RGBColor colorTL;
              Geom::RGBColor colorTR;
              Geom::RGBColor colorBL;
              Geom::RGBColor colorBR;
            };

            Geom::RGBColor color[4];
          };
        };
      };
    };

    struct SpriteBuffer
    {
      struct Header
      {
        union
        {
          BYTE data[32];

          struct
          {
            INT length;
          };
        };
      }
      header;

      SpriteData* sprites;
    };

    struct DLLEXPORT SpriteMap : Map
    {
    public:
      struct Header
      {
        union
        {
          BYTE data[32];

          struct
          {
          };
        };
      }
      headerSprite;

      SpriteBuffer*   buffer;

      virtual BOOL save(HANDLE iFile);
      virtual BOOL open(HANDLE iFile);
      virtual VOID free();
      virtual VOID setSize(INT cx, INT cy, INT layers);

      INL SpriteBuffer* getBuffer() const
      {
        return buffer;
      }

      INL SpriteMap() : buffer(NULL) {}
    };

    class DLLEXPORT RetroProject
    {
    private:
      struct Header
      {
        TCHAR name[RETRO_MAXNAME];
        INT   gameWidth;
        INT   gameHeight;
      };

      Header mHeader;

      Generic::Vector<Resource*> mResources;
      BOOL  mOpened;

      VOID  openResources();

    public:
      INL PCTSTR getName() const
      {
        return mHeader.name;
      }
      INL VOID  setName(PCTSTR value)
      {
        memcpy(mHeader.name, value, sizeof(TCHAR) * ARRAYSIZE(mHeader.name));
      }

      INL INT   getGameWidth() const
      {
        return mHeader.gameWidth;
      }
      INL VOID  setGameWidth(INT value)
      {
        mHeader.gameWidth = value;
      }

      INL INT   getGameHeight() const
      {
        return mHeader.gameHeight;
      }
      INL VOID  setGameHeight(INT value)
      {
        mHeader.gameHeight = value;
      }

      INL BOOL isOpened() const
      {
        return mOpened;
      }

      INL const Generic::Vector<Resource*>& getResources()
      {
        return mResources;
      }

      INL VOID  addResource(Resource* data)
      {
        mResources.add(data);
      }
      INL VOID  removeResource(Resource* data)
      {
        mResources.remove(mResources.getIndex(data));
      }

      Resource* createResource(ResourceType type);

      Resource* findResource(
        ResourceType type,
        INT id
      ) const;
      Resource* findResource(
        ResourceType type,
        PCTSTR name
      ) const;

      VOID saveResources();
      VOID uninitDeletedResources();
      BOOL hasUnsavedResources() const;
      INT getNextResourceID(ResourceType type) const;

      VOID init();
      BOOL save(PCTSTR iFilename);
      BOOL open(PCTSTR iFilename);
      VOID close();

      RetroProject();
    };

    PCTSTR DLLEXPORT GetResourceFolder(ResourceType type);
    PCTSTR DLLEXPORT GetResourceName(ResourceType type);
    PCTSTR DLLEXPORT GetResourceExt(ResourceType type);
  } // namespace Data
} // namespace Retro

#endif